package com.example.compiler;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

/**
 * @author tgw
 * @date 2021/5/18
 * @describe 多个个activity中的拼接的
 * <p>
 * 参考：https://www.jianshu.com/p/40c62ebf53e0
 * 存在坑 多个注解的时候  应该用for循环拼接findviewbyid的操作，以绑定多个
 */

@AutoService(Processor.class)
//@SupportedAnnotationTypes("com.example.compiler.BindView")  //该注解与 下面的getSupportedAnnotationTypes（）方法一个含义
public class ViewBindProcessor extends AbstractProcessor {
    private Filer mFiler;
    private Messager mMessager;
    private Elements mElementUtils;
    private HashMap<String, ViewBindParamBean> mActivityMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
        mMessager = processingEnvironment.getMessager();
        mElementUtils = processingEnvironment.getElementUtils();
    }

    /**
     * 指定使用的Java版本，通常这里返回SourceVersion.latestSupported()
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 指定这个注解处理器是注册给哪个注解的，这里说明是注解BindView
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        supportTypes.add(ViewBind.class.getCanonicalName());
        return supportTypes;
    }


    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        mActivityMap.clear();
        if (set != null && set.size() > 0) {
            Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(ViewBind.class);
            String packName = null;
            String className = null;
            for (Element element : bindViewElements) {
                //1.获取包名
                PackageElement packageElement = mElementUtils.getPackageOf(element);
                packName = packageElement.getQualifiedName().toString();
                print(String.format("package = %s", packName));
                print("注解数量" + bindViewElements.size());


                //2.注解所在的类的类名
                TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
                className = enclosingElement.getSimpleName().toString();
                print(String.format("类名 enclosindClass = %s", enclosingElement));

                if (!mActivityMap.containsKey(enclosingElement.toString())) {
                    ViewBindParamBean bean = new ViewBindParamBean();
                    bean.packageName = packName;
                    bean.className = className;
                    mActivityMap.put(enclosingElement.toString(), bean);
                    print("类的类名" + enclosingElement.toString());
                }


                //因为BindView只作用于filed，所以这里可直接进行强转
                VariableElement bindViewElement = (VariableElement) element;
                //3.获取注解的成员变量名
                String fieldName = bindViewElement.getSimpleName().toString();
                print(String.format("成员变量名 enclosindClass = %s", fieldName));
                //4.获取注解元数据
                ViewBind bindView = element.getAnnotation(ViewBind.class);
                int id = bindView.value();
                print(String.format("%s = %d", fieldName, id));
            }

            //1. 开始拼接代码 /**字符串普通方式拼接*/
            creatMethod(bindViewElements);

            //2.javapoet框架拼接
//            creatMethodPoet(bindViewElements);

            return true;
        }


        return false;
    }

    /**
     * 1.字符串普通方式拼接
     */
    private void creatMethod(Set<? extends Element> bindViewElements) {
        String packName;
        String className;
        for (Map.Entry<String, ViewBindParamBean> entry : mActivityMap.entrySet()) {
            ViewBindParamBean viewBindParamBean = entry.getValue();
            //类的完整名称
            String classKey = entry.getKey();

            packName = viewBindParamBean.packageName;
            className = viewBindParamBean.className;
            print("遍历map：className=" + className + "packName=" + packName);
            print("遍历map：" + mActivityMap.size());
            try {
                String newClassName = className + "$$BindAdapterImp";
                JavaFileObject jfo = mFiler.createSourceFile(packName + "." + newClassName, new Element[]{});
                Writer writer = jfo.openWriter();
                writer.write("package " + packName + ";");
                writer.write("\n\n");
                writer.write("import com.example.bottonknife.BindAdapter;");
//                writer.write("\n\n");
//                writer.write("import "+classKey+";");
                writer.write("\n\n\n");
                writer.write("public class " + newClassName + " implements BindAdapter<" + className + "> {");
                writer.write("\n\n");
                writer.write("public void bind(" + className + " target) {");
                for (Element element : bindViewElements) { //这里是所有的 使用了注解的元素未区分 不同包与类
                    //1.因为BindView只作用于filed，所以这里可直接进行强转
                    VariableElement bindViewElement = (VariableElement) element;
                    //2.注解所在的类的类名
                    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
                    //3.匹配对应类下面的元素
                    if (classKey.equals(enclosingElement.toString())) {
                        //3.获取注解的成员变量名
                        String fieldName = bindViewElement.getSimpleName().toString();

                        //4.获取注解元数据
                        ViewBind bindView = element.getAnnotation(ViewBind.class);
                        int id = bindView.value();
                        print("有多个view绑定");
                        //绑定注解
                        writer.write("\n\n");
                        writer.write("target." + fieldName + " = target.findViewById(" + id + ");");
                    }

                }
                writer.write("\n");
                writer.write("  }");
                writer.write("\n\n");
                writer.write("}");
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * javapoet 用法  参考：https://blog.csdn.net/qq_26376637/article/details/52374063
     */
    private void creatMethodPoet(Set<? extends Element> bindViewElements) {
        String packName;
        String className;
        for (Map.Entry<String, ViewBindParamBean> entry : mActivityMap.entrySet()) {
            ViewBindParamBean viewBindParamBean = entry.getValue();
            //类的完整名称
            String classKey = entry.getKey();

            packName = viewBindParamBean.packageName;
            className = viewBindParamBean.className;
            print("遍历map：className=" + className + "packName=" + packName);
            print("遍历map：" + mActivityMap.size());


            String newClassName = className + "$$BindAdapterImp";
            //获取继承的接口
            final ClassName iBindAdapter = ClassName.get("com.example.bottonknife", "BindAdapter");
            //获取继承的接口中的参数
            ClassName iBindAdapterParam = ClassName.bestGuess(className);

            //拼接方法中的内容
            String content = "";
            for (Element element : bindViewElements) { //这里是所有的 使用了注解的元素未区分 不同包与类
                //1.因为BindView只作用于filed，所以这里可直接进行强转
                VariableElement bindViewElement = (VariableElement) element;
                //2.注解所在的类的类名
                TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
                //3.匹配对应类下面的元素
                if (classKey.equals(enclosingElement.toString())) {
                    //3.获取注解的成员变量名
                    String fieldName = bindViewElement.getSimpleName().toString();
                    //4.获取注解元数据
                    ViewBind bindView = element.getAnnotation(ViewBind.class);
                    int id = bindView.value();
                    content = content + "target." + fieldName + " = target.findViewById(" + id + ");\n\n";
                }
            }

            MethodSpec main = MethodSpec.methodBuilder("bind")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addParameter(iBindAdapterParam, "target")
                    .addStatement(content)
                    .build();

            TypeSpec bind = TypeSpec.classBuilder(newClassName)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(main)
                    .addSuperinterface(ParameterizedTypeName.get(iBindAdapter, iBindAdapterParam)) //添加继承
                    .build();
            JavaFile javaFile = JavaFile.builder(packName, bind).build();

            try {
                //　生成文件
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }


    /**
     * 打印编译期间的日志
     **/
    private void print(String msg) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
    }


}
